package day16;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*需求:改造售票案例,解决多线程数据不安全的问题*/
public class TestRunnable {
    public static void main(String[] args) {
        //5.创建目标业务对象
        TicketRunnable target = new TicketRunnable();
        //6.使用Thread类中的含参构造,将目标对象与线程对象做绑定
//        Thread t1 = new Thread(target);
//        Thread t2 = new Thread(target);
//        Thread t3 = new Thread(target);
//        Thread t4 = new Thread(target);
        //7.以多线程的方式启动线程
//        t1.start();
//        t2.start();
//        t3.start();
//        t4.start();
        /*ExecutorService:用来存储线程的池子,把新建线程/启动线程/关闭线程的任务都交给池来管理*/
        /*Executors:用来创建线程池对象的工具
        * newFixedThreadPool(5);表示创建含有5个线程数的池子*/
        //8.创建线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(5);
        //9.通过循环,让线程池中的线程开始执行目标业务
        for(int i= 0;i <5 ;i++){
            /*execute(target),让线程池中的线程来执行任务,每次调用都会启动一个线程*/
            pool.execute(target);/*本方法的参数就是要执行的目标业务target*/
        }

    }
}
/*1.多线程中出现数据安全隐患的原因:
*多线程程序 + 有共享数据(成员变量) + 多条语句操作共享数据 */
/*2.同步锁:相当于给容易出现问题的代码加了一把锁,包裹了所有可能会出现安全隐患的代码
加锁之后,就有同步(排队)的效果,但是加锁的范围,需要考虑:
不能太大,太大,干啥都得排队,效率低,范围太小,太小,锁不住,还是会有安全隐患*/
//1.创建自定义多线程类
class TicketRunnable implements Runnable{
    //3.创建成员变量,用来保存票数,注意必须是静态的
    static int tickets = 100;
    //8.2创建一个唯一的锁对象
    /*5.不论之后是哪个线程进同步代码块,使用的都是唯一的锁对象,"唯一"很重要*/
    Object o = new Object();
    //2.添加接口中未实现的方法,把业务放在run()里
    @Override
    /*6.如果一个方法中的所有代码都需要同步,那这个方法可以设置成同步方法*/
    //synchronized public void run() {/*被sychronized关键字修饰的方法是同步方法*/
    public void run() {/*被sychronized关键字修饰的方法是同步方法*/
        while (true){
            //8.1使用同步代码块包裹可能会出现安全隐患的代码,锁对象类型任意
            /*3.同步代码块:sychronized(锁对象){会出现安全问题的代码}
             * 同步代码块中,同一时刻,同一资源,只能被一个线程独享*/
            /*4.这种写法不对,相当于每个线程进来都会new一个锁对象,线程间并不是使用同一把锁*/
            //synchronized (new Object()) {
            //8.3修改同步代码块的锁对象为成员变量o,注意,"唯一"很重要
            synchronized (o) {
                //3.获取当前正在卖票的线程名称,以及卖票
                if(tickets > 0){//还有票的情况下就卖票

                    try {
                        //让程序休眠后出现的两个问题:
                        //问题1.重卖:一张票卖给了多个人
                        //问题2.超卖:出现了票数为0甚至是负数的情况
                        Thread.sleep(10);//让程序休眠10ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "=" + tickets--);
                }
                //4.设置死循环的出口
                if (tickets <= 0) break;
            }
        }
    }
}